#include "Socket.h"
#include "SocketError.hpp"

namespace nio {

	Socket::Socket(SocketFamily family, SocketType type, int protocol) {
		fd_ = socket(static_cast<int>(family), static_cast<int>(type), protocol);
		if (fd_ == INVALID_FD)
			throw SocketError("Created socket failed!", Errno);
	}

	Socket::Socket(SOCKET socket) {
		fd_ = socket;
	}

	Socket::~Socket() {
		Close();
	}

	void Socket::Bind(const char* ip, int port) {
		struct sockaddr_in serverAddr {};
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.s_addr = inet_addr(ip);
		serverAddr.sin_port = htons(port);

		int ret = bind(fd_, (const sockaddr*)&serverAddr, sizeof(serverAddr));
		if (ret < 0) {
			throw SocketError("Bind failed!", Errno);
		}
	}

	void Socket::Listen(int backlog) {
		int ret = listen(fd_, backlog);
		if (ret < 0) {
			throw SocketError("Listen failed!", Errno);
		}
	}

	Socket* Socket::Accept() {
		SOCKET newfd = accept(fd_, nullptr, nullptr);
		if (newfd == INVALID_FD) {
			throw SocketError("Accept failed!", Errno);
		}
		return new Socket(newfd);
	}

	void Socket::Connect(sockaddr* addr, int len) {
		int ret = connect(fd_, addr, len);
		if (ret < 0) {
#ifdef _WIN32
			if (Errno == WSAEWOULDBLOCK)return;
#else
			if (Errno == EINPROGRESS)return;
#endif
			throw SocketError("Connect failed!", Errno);
		}
	}

	void Socket::Connect(const char* ipv4, int port) {
		struct sockaddr_in serverAddr {};
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.s_addr = inet_addr(ipv4);
		serverAddr.sin_port = htons(port);

		Connect((sockaddr*)&serverAddr, sizeof(serverAddr));
	}

	int Socket::Recv(char* buf, int len, int flags) {
		return (int)recv(fd_, buf, len, flags);
	}

	int Socket::Send(char* buf, int len, int flags) {
		return (int)send(fd_, buf, len, flags);
	}

	int Socket::Recvfrom(char* buf, int len, sockaddr* from, int* fromlen, int flags) {
#ifdef _WIN32
		return recvfrom(fd_, buf, len, flags, from, fromlen);
#else
		return (int)recvfrom(fd_, buf, len, flags, from, (socklen_t*)fromlen);
#endif
	}

	int Socket::Sendto(char* buf, int len, sockaddr* to, int tolen, int flags) {
		return (int)sendto(fd_, buf, len, flags, to, tolen);
	}

	void Socket::Close() {
		if (fd_ == INVALID_FD)return;
#ifdef _WIN32
		closesocket(fd_);
#else
		close(fd_);
#endif
		fd_ = INVALID_FD;
	}

	void Socket::ReuseAddr(bool enable) {
		int value = enable;
		if (setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, (const char*)&value, sizeof(value)) < 0) {
			throw SocketError("ReuseAddr failed!", Errno);
		}
	}

	void Socket::SetNonblocking(bool enable) {
#ifdef _WIN32
		u_long value = enable;
		if (ioctlsocket(fd_, FIONBIO, &value) < 0) {
			throw SocketError("SetNonblocking failed!", Errno);
		}
#else
		int flags = fcntl(fd_, F_GETFL, 0);
		if (flags < 0) {
			throw SocketError("fcntl get failed!", Errno);
		}
		if (enable) {
			flags = O_NONBLOCK | flags;
		}
		else {
			flags = ~O_NONBLOCK & flags;
		}
		if (fcntl(fd_, F_SETFL, flags) < 0) {
			throw SocketError("fcntl set failed!", Errno);
		}
#endif
	}


}